Android — 自定义Behavior实现Toolbar颜色渐变

前言

上一篇中了解了协调布局CoordinatorLayout,可以协调子View之间的交互,那么它是怎么协调的呢?核心就是Behavior。同样的,上一节中,在处理AppbarLayout与NestedScrollView联动的时候,我们给NestedScrollView设置了一个Behavior,即:

1
app:layout_behavior="@string/appbar_scrolling_view_behavior"

这个Behavior是系统内置的。

这一节就自定义Behavior实现Toolbar颜色渐变,一图胜前言:

正文

实现起来也很简单,一句话概述就是根据偏移值来对Toolbar的背景色设置透明度。

自定义Behavior需要继承该类并重写相关方法。以下是几个比较重要的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* 表示是否给应用了Behavior 的View 指定一个依赖的布局,通常,当依赖的View 布局发生变化时
* 不管被被依赖View 的顺序怎样,被依赖的View也会重新布局
* @param parent
* @param child 绑定behavior 的View
* @param dependency 依赖的view
* @return 如果child 是依赖的指定的View 返回true,否则返回false
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency);
}
/**
* 当被依赖的View 状态(如:位置、大小)发生变化时,这个方法被调用
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return super.onDependentViewChanged(parent, child, dependency);
}
/**
* 当coordinatorLayout 的子View试图开始嵌套滑动的时候被调用。当返回值为true的时候表明
* coordinatorLayout 充当nested scroll parent 处理这次滑动,需要注意的是只有当返回值为true
* 的时候,Behavior 才能收到后面的一些nested scroll 事件回调(如:onNestedPreScroll、onNestedScroll等)
* 这个方法有个重要的参数nestedScrollAxes,表明处理的滑动的方向。
*
* @param coordinatorLayout 和Behavior 绑定的View的父CoordinatorLayout
* @param child 和Behavior 绑定的View
* @param directTargetChild
* @param target
* @param nestedScrollAxes 嵌套滑动 应用的滑动方向,看 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
* {@link ViewCompat#SCROLL_AXIS_VERTICAL}
* @return
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
/**
* 嵌套滚动发生之前被调用
* 在nested scroll child 消费掉自己的滚动距离之前,嵌套滚动每次被nested scroll child
* 更新都会调用onNestedPreScroll。注意有个重要的参数consumed,可以修改这个数组表示你消费
* 了多少距离。假设用户滑动了100px,child 做了90px 的位移,你需要把 consumed[1]的值改成90,
* 这样coordinatorLayout就能知道只处理剩下的10px的滚动。
* @param coordinatorLayout
* @param child
* @param target
* @param dx 用户水平方向的滚动距离
* @param dy 用户竖直方向的滚动距离
* @param consumed
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
/**
* 进行嵌套滚动时被调用
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed target 已经消费的x方向的距离
* @param dyConsumed target 已经消费的y方向的距离
* @param dxUnconsumed x 方向剩下的滚动距离
* @param dyUnconsumed y 方向剩下的滚动距离
*/
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
/**
* 嵌套滚动结束时被调用,这是一个清除滚动状态等的好时机。
* @param coordinatorLayout
* @param child
* @param target
*/
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
/**
* onStartNestedScroll返回true才会触发这个方法,接受滚动处理后回调,可以在这个
* 方法里做一些准备工作,如一些状态的重置等。
* @param coordinatorLayout
* @param child
* @param directTargetChild
* @param target
* @param nestedScrollAxes
*/
@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
/**
* 用户松开手指并且会发生惯性动作之前调用,参数提供了速度信息,可以根据这些速度信息
* 决定最终状态,比如滚动Header,是让Header处于展开状态还是折叠状态。返回true 表
* 示消费了fling.
*
* @param coordinatorLayout
* @param child
* @param target
* @param velocityX x 方向的速度
* @param velocityY y 方向的速度
* @return
*/
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
//可以重写这个方法对子View 进行重新布局
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
return super.onLayoutChild(parent, child, layoutDirection);
}
作者:依然范特稀西
链接:https://www.jianshu.com/p/82d18b0d18f4
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

了解之后,代码就很好写了,首先是布局文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar_home"
android:background="@color/colorHomeHeaderBg"
app:layout_behavior="top.omooo.blackfish.utils.ToolbarAlphaBehavior"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:scaleType="fitXY"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dp"
android:src="@drawable/icon_home_header_fish_logo"
android:layout_width="70dp"
android:layout_height="23dp"/>
<TextView
android:text="小黑鱼"
android:textColor="@color/splash_main_title_color"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dp"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/iv_home_header_msg"
android:src="@drawable/icon_home_header_msg_white"
android:layout_alignParentBottom="true"
android:layout_marginBottom="10dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="20dp"
android:scaleType="fitXY"
android:layout_width="20dp"
android:layout_height="20dp"/>
</RelativeLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CoordinatorLayout>

ToolbarAlphaBehavior.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class ToolbarAlphaBehavior extends CoordinatorLayout.Behavior<Toolbar> {
private static final String TAG = "ToolbarAlphaBehavior";
private int offset = 0;
private int startOffset = 0;
private int endOffset = 0;
private Context mContext;
public ToolbarAlphaBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull Toolbar child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
return true;
}
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull Toolbar child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
endOffset = child.getHeight();
offset += dyConsumed;
RelativeLayout relativeLayout = (RelativeLayout) child.getChildAt(0);
ImageView imageLogo = (ImageView) relativeLayout.getChildAt(0);
TextView textTitle = (TextView) relativeLayout.getChildAt(1);
ImageView imageMsg = (ImageView) relativeLayout.getChildAt(2);
// TODO: 2018/4/1 imageMsg从白变黑,而不是从有到无
if (offset <= startOffset) { //alpha为0
child.getBackground().setAlpha(0);
Log.i(TAG, "onNestedScroll: " + textTitle.getText());
textTitle.setAlpha(0);
} else if (offset > startOffset && offset < endOffset) { //alpha为0到255
imageLogo.setImageDrawable(mContext.getDrawable(R.drawable.icon_home_header_fish_logo));
imageMsg.setImageDrawable(mContext.getDrawable(R.drawable.icon_home_header_msg_white));
float precent = (float) (offset - startOffset) / endOffset;
int alpha = Math.round(precent * 255);
child.getBackground().setAlpha(alpha);
textTitle.setVisibility(View.VISIBLE);
textTitle.setAlpha(precent);
imageLogo.getDrawable().setAlpha(1 - alpha);
imageMsg.getDrawable().setAlpha(1-alpha);
} else if (offset >= endOffset) { //alpha为255
child.getBackground().setAlpha(255);
textTitle.setVisibility(View.VISIBLE);
textTitle.setAlpha(1);
imageMsg.setImageDrawable(mContext.getDrawable(R.drawable.icon_home_header_msg_black));
imageMsg.setAlpha(1f);
}
}
}

最后,要在布局初始化的时候把Toolbar背景色设置为全透明。

1
2
mToolbar = findView(R.id.toolbar_home);
mToolbar.getBackground().setAlpha(0);

完。

参考

当然,Behavior还是很多好玩的东西,可以看看这些文章:

Material Design 之 Behavior的使用和自定义Behavior

关于BottomSheetBehavior、BottomSheetDialog和SwipeDismissBehavior

我们一直都向往,面朝大海,春暖花开。 但是几人能做到,心中有爱,四季不败?